#include "defines.h"
#include "kozos.h"
#include "softvec.h"
#include "serial.h"
#include "lib.h"
#include "consdrv.h"

#define SEND_BUFFER_SIZE 256

static unsigned char *send_buf; // リングバッファ
static int send_head;
static int send_len;

static kz_msgbox_id_t msgbox_use;
static kz_msgbox_id_t msgbox_recv;
static kz_msgbox_id_t msgbox_send;

typedef struct {
    char cmd;
    int size;
} cons_write_header;

int cons_use(kz_msgbox_id_t mb)
{
    kz_send(msgbox_recv, mb, "u", 0, 0);
    return 0;
}

int cons_putc(int c)
{
    return kz_send(msgbox_send, c, "p", 1, 8);
}

int cons_write(const char *s, int size)
{
    if (size <= 0) {
        return 0;
    } else if (size == 1) {
        return cons_putc(s[0]);
    } else {
        cons_write_header *p;
        int ret;

        p = kz_kmalloc(sizeof(cons_write_header) + size);
        p->cmd = 'w';
        p->size = size;
        memcpy(p + 1, s, size);
        ret = kz_send(msgbox_send, 0, (char *)p, 1, 8);
        if (ret == -1) kz_kmfree(p);

        return ret;
    }
}

int cons_close(kz_msgbox_id_t mb)
{
    char *q;
    kz_send(msgbox_recv, mb, "c", 0, 0);
    do {
        kz_recv(mb, NULL, &q);
    } while (q[0] != 'c');
    return 0;
}

static void send_char()
{
    INTR_DISABLE; // ssrのビット操作の排他のため
    serial_send_byte(send_buf[send_head]);
    INTR_ENABLE;
    if (++send_head == SEND_BUFFER_SIZE)
        send_head = 0;
    send_len--;
}

static void buffer_char(unsigned char c)
{
    if (send_len < SEND_BUFFER_SIZE - (c == '\n')) {
        if (send_len == 0) serial_intr_send_enable();
        if (c == '\n') send_buf[(send_head + send_len++) % SEND_BUFFER_SIZE] = '\r';
        send_buf[(send_head + send_len++) % SEND_BUFFER_SIZE] = c;
    } else {
        INTR_DISABLE;
        puts_k("\nsend_buf overflow !!\n");
        INTR_ENABLE;
    }
}

static void consrecv_intr()
{
    while (1) {
        if (serial_is_error()) {
            puts("\nserial error !!\n");
            serial_clear();
        } else if (serial_is_recv_enable()) {
            kz_send(msgbox_recv, serial_recv_byte_nocheck(), "<", 1, 8);
            continue; // 続けて受信することで矢印キー等複数バイト送信時、オーバーランを起きにくくする
        }
        return;
    }
}

static void conssend_intr()
{
    kz_send(msgbox_send, 0, ">", 0, 0);
    serial_intr_send_disable();
}

static void consrecv_main(int argc, char *argv)
{
    int c;
    char *p;

    while (1) {
        kz_recv(msgbox_recv, &c, &p);

        if (p[0] == '<') {
            if (msgbox_use != MSGBOX_NULL) {
                kz_send(msgbox_use, c, "w", 0, 16);
            } else { // エコーバック
                if (c == '\r') c = '\n';
                cons_putc(c);
            }

        } else if (p[0] == 'u') {
            ASSERT(msgbox_use == MSGBOX_NULL, "cons is used");
            msgbox_use = c;

        } else if (p[0] == 'c') {
            ASSERT(msgbox_use == c, "can't close cons");
            msgbox_use = MSGBOX_NULL;
            kz_send(c, 0, "c", 0, 0);

        } else {
            ASSERT(0, "consrecv: Unknown cmd");
        }
    }

    ASSERT(0, "consrecv exit"); /* ここには来ない */
}

static void conssend_main(int argc, char *argv)
{
    int c;
    char *p;

    while (1) {
        kz_recv(msgbox_send, &c, &p);

        if (p[0] == '>') {
            send_char(); // send_lenが1減る!
            if (send_len) serial_intr_send_enable();

        } else if (p[0] == 'p') {
            buffer_char(c);

        } else if (p[0] == 'w') {
            cons_write_header *hdr = (cons_write_header *)p;
            unsigned char *q = (unsigned char *)(hdr + 1);
            while (hdr->size-- > 0) {
                buffer_char(*q++);
            }
            kz_kmfree(hdr);

        } else {
            ASSERT(0, "conssend: Unknown cmd");
        }
    }

    ASSERT(0, "conssend exit"); /* ここには来ない */
}

void consdrv_main(void)
{
    msgbox_use = MSGBOX_NULL;
    msgbox_recv = kz_getmsgbox();
    msgbox_send = kz_getmsgbox();

    send_buf = kz_kmalloc(SEND_BUFFER_SIZE);
    send_len = 0;
    send_head = 0;

    kz_setintr(SOFTVEC_TYPE_SERRECV1, consrecv_intr);
    kz_setintr(SOFTVEC_TYPE_SERSEND1, conssend_intr);
    serial_intr_recv_enable();

    kz_run(consrecv_main, "consrecv", 2, 0x100, 0, NULL);
    kz_run(conssend_main, "conssend", 2, 0x100, 0, NULL);
}
